﻿using BlogDemo.Api.Authenticate;
using BlogDemo.Application;
using BlogDemo.Application.BlogApp;
using BlogDemo.Application.PostApp;
using BlogDemo.Application.PostApp.Validation;
using BlogDemo.Core.Repository;
using BlogDemo.Data;
using BlogDemo.Infrastructure;
using BlogDemo.Infrastructure.Repository;
using FluentValidation.AspNetCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using NLog;
using System;
using System.Text;

namespace BlogDemo.Api
{
    public class Startup
    {
        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration)
        {
            AppSettings.Init(configuration);
            Configuration = configuration;
        }


        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddOptions();
            services.Configure<ApplicationConfiguration>(Configuration.GetSection("ApplicationConfiguration"));
            services.Configure<TokenConfiguration>(Configuration.GetSection("TokenConfiguration"));
            services.AddScoped<AppConfigurtaionService>();
            services.AddDistributedMemoryCache();
            AppSettings.Init(Configuration);

            String connectionString = Configuration.GetConnectionString("Sqlite");
            services.AddDbContext<IDbContext, BlogDbContext>(opts =>
            {
                opts.UseSqlite(connectionString, b => b.MigrationsAssembly("BlogDemo.Infrastructure"));
            });
            services.AddScoped<IUnitOfWork, EfRepositoryContext>();
            services.AddScoped<IEfRepositoryContext, EfRepositoryContext>();
            services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
            services.AddScoped(typeof(IRepository<,>), typeof(EfRepository<,>));
            services.AddScoped(typeof(IAppService<,>), typeof(AppServiceBase<,>));
            services.AddScoped(typeof(IAppService<,,>), typeof(AppServiceBase<,,>));

            services.AddScoped<IPostRepostory, PostRepository>();
            services.AddScoped<IBlogRepostory, BlogRepostory>();
            services.AddScoped<ITagRepostory, TagRepostory>();

            services.AddScoped<IBlogAppService, BlogAppService>();
            services.AddScoped<IPostAppService, PostAppService>();

            services.AddSingleton<IAuthenticateService, JwtAuthenticationService>();
            // services.AddTransient<IValidator<Person>, PersonValidator>();
            //etc
            // builder.Services.AddMultiTenancy()
            //             .WithResolutionStrategy<HostResolutionStrategy>()
            //             .WithStore<InMemoryTenantStore>();


            services.AddHttpsRedirection(opt =>
            {
                opt.RedirectStatusCode = 307;
                opt.HttpsPort = 5001;

            });
            services.AddMvc();
            services.AddControllers(configure =>
            {
                configure.ReturnHttpNotAcceptable = true;

            }).AddXmlDataContractSerializerFormatters();
            services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());


            #region  添加FluentValidation验证
            services.AddControllersWithViews()
                       .AddFluentValidation(config =>//添加FluentValidation验证
                       {
                           //程序集方式添加验证
                           config.RegisterValidatorsFromAssemblyContaining(typeof(PostAddResourceValidator));
                           //是否与MvcValidation共存，设置为false后将不再执行特性方式的验证
                           config.DisableDataAnnotationsValidation = false;
                       });
            #endregion

            #region  Cors
            //string[] origins = Configuration.GetValue<string>("CorsOrigin").Split(","); //从appsettings.json读取
            //services.AddCors(c =>
            //c.AddPolicy("AllowSpecificOrigin", policy =>
            //{
            //    policy.WithOrigins(origins).WithMethods("GET", "POST", "PUT", "DELETE");
            //}));
            #endregion

            // Cookie, JwtBearer, OAuth, OpenIdConnect 
            var token = Configuration.GetSection("TokenConfiguration").Get<TokenConfiguration>();

            //配置认证服务
            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;


            }).AddJwtBearer(o =>
            {
                o.TokenValidationParameters = new TokenValidationParameters
                {
                    //是否验证发行人
                    ValidateIssuer = true,
                    ValidIssuer = token.Issuer,//发行人
                                               //是否验证受众人
                    ValidateAudience = true,
                    ValidAudience = token.Audience,//受众人
                    //是否验证密钥
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.SigningKey)),

                    ValidateLifetime = true, //验证生命周期
                    RequireExpirationTime = true, //过期时间
                };
            });
            #region 配置 授权 

            //Razor Pages授权约定
            //简单授权   [Authorize] [AllowAnonymous]
            //角色授权   [Authorize(Roles = "HRManager,Finance")]
            //声明授权   [Authorize(Policy = "EmployeeOnly")]
            //策略授权   [Authorize(Policy = "AtLeast21")]
            ////Policy、Role、Scheme
            //services.AddAuthorization(options =>
            //{
            //1、Definition authorization policy
            // options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
            //或者一个策略带有claimType类型和requiredValues值
            //options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
            //(1) 添加Claims策略
            //(2) 将policy策略名称应用在控制器上
            //(3) 用户登录，访问Employee资源，访问拒绝
            //(4) 在UserClaim声明表插入一条数据(上面注册EmployeeOnly声明策略，只要求验证claimType类型。所以表中claimvalue的1值不启作用， 如果要表中claimvalue启作用，可以在注册EmployeeOnly时输入requiredValues值)
            //(5) 用户退出，再登录，访问Employee资源，OK


            // MinimumAgeRequirement参数对象 实现了IAuthorizationRequirement
            //options.AddPolicy("AtLeast21", policy => policy.Requirements.Add(new MinimumAgeRequirement(21)));

            //(1) 注册策略授权
            // 创建了一个名为"AtLeast21"的策略授权，这个策略的需求是最小年龄需求，策略通过参数对象(IAuthorizationRequirement)提供，它要求最低年龄是21岁。
            //(2) 策略授权应用到mvc的控制器或Razor Pages
            //用户购买酒业务, 策略授权应用到控制器，要求用户年龄不能低于21岁
            //[Authorize(Policy = "AtLeast21")]
            //public class AlcoholPurchaseController : Controller
            //{
            //    public IActionResult Index() => View();
            //}
            ////策略授权到razor pages的PageModel类
            //[Authorize(Policy = "AtLeast21")]
            //public class AlcoholPurchaseModel : PageModel
            //{
            //}

            //(3) Requirement策略授权需求

            //策略授权需求实现IAuthorizationRequirement接口, 用于策略需求对象参数传递。MinimumAgeRequirement就是一个需求参数对象

            //(4) 策略授权处理程序类
            //授权处理程序负责评估要求的属性(指策略授权逻辑处理，把当前用户的年龄与策略要求年龄进行验证）。 授权处理程序会针对提供的AuthorizationHandlerContext 来评估要求，确定是否允许访问或拒绝。
            //实现策略授权处理程序,需要继承AuthorizationHandler<TRequirement>,其中TRequirement就是参数对象。另外，一个处理程序也可以通过实现 IAuthorizationHandler 来处理多个类型的要求。

            //下面是一对一关系的示例(一个Handler处理一个TRequirement对象)，评估最低年龄要求：MinimumAgeHandler
            // 上面代码是当前用户主休是否有一个由已知的受信任颁发者(Issuer)颁发的出生日期声明(ClaimTypes.DateOfBirth)。
            // 当前用户缺少声明时，无法进行授权，这种情况下会返回已完成的任务。如果存在声明时，会计算用户的年龄。
            // 如果用户满足此要求所定义的最低年龄，则可以认为授权成功。 授权成功后，会调用 context.Succeed，使用满足的要求作为其唯一参数。


            //(5) 处理程序注入到服务集合，采用单例
            //在UserClaim用户声明表中，保存一条符合该策略授权的数据，当启动程序，访问AlcoholPurchase资源时，进入授权处理程序MinimumAgeHandler中， 执行context.Succeed(requirement)后， 授权成功


            //}).AddAuthentication(s =>
            //        {
            //            //2、Authentication
            //            s.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            //            s.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
            //            s.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            //        }).AddJwtBearer(options =>
            //        {
            //            //3、Use Jwt bearer 
            //            options.TokenValidationParameters = new TokenValidationParameters()
            //            {
            //                ValidateIssuer = true,
            //                ValidIssuer = token.Issuer,
            //                ValidateAudience = true,
            //                ValidAudience = token.Audience,
            //                ValidateIssuerSigningKey = true,
            //                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(token.SigningKey))
            //            };

            //            options.Events = new JwtBearerEvents
            //            {
            //                OnAuthenticationFailed = context =>
            //                {
            //                    //Token expired
            //                    if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
            //                    {
            //                        context.Response.Headers.Add("Token-Expired", "true");
            //                    }
            //                    return Task.CompletedTask;
            //                }
            //            };
            //        });

            #endregion

            //services.AddSingleton<IAuthorizationHandler, BadgeEntryHandler>();
            //services.AddSingleton<IAuthorizationHandler, TemporaryStickerHandler>();

            //#region 配置Swagger服务
            //services.AddSwagger();
            //    #endregion
        }

        /// <summary>
        /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        /// </summary>
        /// <param name="app"></param>
        /// <param name="env"></param>
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            LogManager.Configuration.Variables["connectionString"] = Configuration.GetConnectionString("DefaultConnection");
            LogManager.Configuration.Variables["configDir"] = Configuration.GetSection("LogFilesDir").Value;

            if (env.IsDevelopment())
            {
                DeveloperExceptionPageOptions developerExceptionPageOptions = new DeveloperExceptionPageOptions();
                developerExceptionPageOptions.SourceCodeLineCount = 5;
                app.UseDeveloperExceptionPage(developerExceptionPageOptions);

                #region 启用swagger
                //app.UseSwagger();
                //app.UseSwaggerUI(c =>
                //{
                //    c.SwaggerEndpoint("swagger/v1/swagger.json", "v1");
                //});
                #endregion

            }
            #region AllowSpecificOrigin
            //app.UseCors("AllowSpecificOrigin");
            #endregion


            //app.UseMiddleware<MultipleTenancyMiddleware>();
            //app.UseMiddleware<ResponseTimeMiddleware>();
            //app.UseMiddleware<ExceptionHandlingMiddleware>();
            app.UseHttpsRedirection();


            app.UseRouting();
            //调用中间件：UseAuthentication（认证），必须在所有需要身份认证的中间件前调用，比如 UseAuthorization（授权）。
            //1.先开启认证
            app.UseAuthentication();
            //2.再开启授权
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}
